/*
 * CPUID
 *
 * A simple and small tool to dump/decode CPUID information.
 *
 * Copyright (c) 2010-2021, Steven Noonan <steven@uplinklabs.net>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 */

#include "prefix.h"

#ifdef TARGET_OS_WINDOWS
#include <windows.h>
#define sched_yield() SwitchToThread()
#else
#define _USE_GNU
#include <pthread.h>
#include <sched.h>
#include <unistd.h>
#include <sys/time.h>
#endif

#include <stdio.h>
#include <string.h>

#include "clock.h"
#include "sanity.h"
#include "state.h"
#include "util.h"

typedef int(*sanity_handler_t)(struct cpuid_state_t *state);

static int sane_apicid(struct cpuid_state_t *state);
static int sane_l3_sharing(struct cpuid_state_t *state);
static int sane_performance(struct cpuid_state_t *state);

#ifdef __GNUC__
#pragma GCC diagnostic ignored "-Wunused-function"
#endif

sanity_handler_t handlers[] = {
	sane_performance,
	sane_apicid,
	//sane_l3_sharing,
	NULL
};

static uint8_t get_apicid(struct cpuid_state_t *state)
{
	struct cpu_regs_t regs;
	ZERO_REGS(&regs);
	regs.eax = 1;
	state->cpuid_call(&regs, state);
	regs.ebx = regs.ebx >> 24;
	return (char)(regs.ebx & 0xFF);
}

static uint8_t get_apicid_for_cpu(struct cpuid_state_t *state, uint32_t index)
{
	state->thread_bind(state, index);
	return get_apicid(state);
}

#ifdef TARGET_OS_WINDOWS
static DWORD WINAPI apic_nonsensical_worker_thread(LPVOID flagptr)
{
	uint8_t *flag = (uint8_t *)flagptr;
	uint32_t hwthreads = thread_count_native(NULL);
	while (*flag) {
		SetThreadAffinityMask(GetCurrentThread(), 1 << (rand() % hwthreads));
		Sleep(1);
	}
	return 0;
}
#else
static void *apic_nonsensical_worker_thread(void *flagptr)
{
	uint8_t *flag = (uint8_t *)flagptr;
	uint32_t hwthreads = thread_count_native(NULL);
	struct timeval tv;
	while (*flag) {
		thread_bind_native(NULL, rand() % hwthreads);
		gettimeofday(&tv, NULL);
	}
	pthread_exit(NULL);
	return NULL;
}
#endif

struct apic_validate_t {
	struct cpuid_state_t *state;
	uint32_t index;
	uint8_t *worker_flag;
	uint8_t expected;
	uint8_t failed;
};

#ifdef TARGET_OS_WINDOWS
static DWORD WINAPI apic_validation_thread(LPVOID ptr)
{
	struct apic_validate_t *meta = (struct apic_validate_t *)ptr;
	SetThreadAffinityMask(GetCurrentThread(), 1 << meta->index);
	while (!meta->failed && *meta->worker_flag) {
		Sleep(5);
		if (get_apicid(meta->state) != meta->expected) {
			meta->failed = 1;
		}
	}
	return 0;
}
#else
static void *apic_validation_thread(void *ptr)
{
	struct apic_validate_t *meta = (struct apic_validate_t *)ptr;
	thread_bind_native(NULL, meta->index);
	while (!meta->failed && *meta->worker_flag) {
		usleep(5000);
		if (get_apicid(meta->state) != meta->expected) {
			meta->failed = 1;
		}
	}
	pthread_exit(NULL);
	return NULL;
}
#endif

static int apic_compare(const void *a, const void *b)
{
	return (*(const uint8_t *)a - *(const uint8_t *)b);
}

#ifdef TARGET_OS_WINDOWS
typedef HANDLE thread_handle_t;
#else
typedef pthread_t thread_handle_t;
#endif

static int sane_apicid(struct cpuid_state_t *state)
{
	int ret = 0;
	uint32_t hwthreads = thread_count_native(NULL), i,
	         worker_count;
	uint8_t *apic_ids = NULL, *apic_copy = NULL, worker_flag;
	struct apic_validate_t *apic_state = NULL;
	double start, now;
	thread_handle_t *busy_workers = NULL,
	                *apic_workers = NULL;

	worker_count = hwthreads / 4 + 1;

	printf("Verifying APIC ID sanity:\n");

	/* Populate initial APIC ID array. */
	apic_ids = (uint8_t *)malloc(hwthreads * sizeof(unsigned char));
	for (i = 0; i < hwthreads; i++) {
		apic_ids[i] = get_apicid_for_cpu(state, i);
	}

	/* First verify that no CPUs reported identical APIC IDs. */
	apic_copy = (uint8_t *)malloc(hwthreads * sizeof(unsigned char));
	memcpy(apic_copy, apic_ids, hwthreads * sizeof(unsigned char));
	qsort(apic_copy, hwthreads, sizeof(unsigned char), apic_compare);
	for (i = 1; i < hwthreads; i++) {
		if (apic_ids[i - 1] == apic_ids[i]) {
			printf("  failed (duplicate APIC IDs detected)\n");
			ret = 1;
			break;
		}
	}
	free(apic_copy);
	apic_copy = NULL;

	/* Spawn a few busy threads to incur thread migrations,
	   if they're going to happen at all. */
	worker_flag = 1;
	busy_workers = (thread_handle_t *)malloc(worker_count * sizeof(thread_handle_t));
	for (i = 0; i < worker_count; i++)
#ifdef TARGET_OS_WINDOWS
		busy_workers[i] = CreateThread(NULL, 0, apic_nonsensical_worker_thread, &worker_flag, 0, NULL);
#else
		pthread_create(&busy_workers[i], NULL, apic_nonsensical_worker_thread, &worker_flag);
#endif

	/* Now verify that the APIC IDs don't change over time. */
	apic_state = (struct apic_validate_t *)malloc(hwthreads * sizeof(struct apic_validate_t));
	apic_workers = (thread_handle_t *)malloc(hwthreads * sizeof(thread_handle_t));
	memset(apic_state, 0, hwthreads * sizeof(struct apic_validate_t));
	for (i = 0; i < hwthreads; i++) {
		apic_state[i].state = state;
		apic_state[i].index = i;
		apic_state[i].expected = apic_ids[i];
		apic_state[i].worker_flag = &worker_flag;
#ifdef TARGET_OS_WINDOWS
		apic_workers[i] = CreateThread(NULL, 0, apic_validation_thread, &apic_state[i], 0, NULL);
#else
		pthread_create(&apic_workers[i], NULL, apic_validation_thread, &apic_state[i]);
#endif
	}
	free(apic_ids);
	apic_ids = NULL;

	/* Occasionally signal workers to run validation checks. */
	start = time_sec();
	while(worker_flag) {
		now = time_sec();
		if (now - start > 3.0)
			break;
#ifdef TARGET_OS_WINDOWS
		Sleep(10);
#else
		usleep(10000);
#endif
		for (i = 0; i < hwthreads; i++) {
			if (apic_state[i].failed) {
				printf("  failed (APIC IDs on CPU %d changed over time)\n", i);
				ret = 2;
				worker_flag = 0;
				break;
			}
		}
	}

	if (ret == 0)
		printf("  passed\n");

	/* Wait for workers to exit. */
	worker_flag = 0;
	if (busy_workers) {
#ifdef TARGET_OS_WINDOWS
		WaitForMultipleObjects(worker_count, busy_workers, TRUE, INFINITE);
		for (i = 0; i < worker_count; i++)
			CloseHandle(busy_workers[i]);
#else
		for (i = 0; i < worker_count; i++) {
			pthread_join(busy_workers[i], NULL);
		}
#endif
		free(busy_workers);
	}

	if (apic_workers) {
#ifdef TARGET_OS_WINDOWS
		WaitForMultipleObjects(hwthreads, apic_workers, TRUE, INFINITE);
		for (i = 0; i < hwthreads; i++)
			CloseHandle(apic_workers[i]);
#else
		for (i = 0; i < hwthreads; i++) {
			pthread_join(apic_workers[i], NULL);
		}
#endif
		free(apic_workers);
	}
	
	free(apic_ids);
	free(apic_copy);
	free(apic_state);

	return ret;
}

static int sane_l3_sharing(struct cpuid_state_t *state)
{
	struct eax_cache04_t {
		unsigned type:5;
		unsigned level:3;
		unsigned self_initializing:1;
		unsigned fully_associative:1;
		unsigned reserved:4;
		unsigned max_threads_sharing:12; /* +1 encoded */
		unsigned apics_reserved:6; /* +1 encoded */
	};
	uint32_t i = 0;
	struct cpu_regs_t regs;
	printf("Verifying L3 thread sharing sanity... ");
	while (1) {
		struct eax_cache04_t *eax;
		ZERO_REGS(&regs);
		regs.eax = 4;
		regs.ecx = i;
		state->cpuid_call(&regs, state);

		if (!(regs.eax & 0xF))
			break;

		eax = (struct eax_cache04_t *)&regs.eax;

		if (eax->level == 3 && eax->max_threads_sharing + 1 == 1) {
			printf("fail (L3 cache shared by too few threads)\n");
			return 0;
		}

		i++;
	}
	printf("ok\n");
	return 0;
}

static int measure_leaf(struct cpuid_state_t *state, uint32_t eax, uint32_t ecx)
{
	uint64_t s, e, d, best;
	uint32_t i, iters = 25;
	uint32_t t, trials = 1000;
	struct cpu_regs_t regs;

	printf("Leaf 0x%08x:%02x  ", eax, ecx);
	best = UINT32_MAX;
	for (t = 0; t < trials; t++) {
		s = get_cpu_clock();
		for (i = 0; i < iters; i++) {
			ZERO_REGS(&regs);
			regs.eax = eax;
			regs.ecx = ecx;
			state->cpuid_call(&regs, state);
		}
		e = get_cpu_clock();
		d = e - s;
		if (d < best)
			best = d;
		if (t % 3 == 0)
			sched_yield();
	}

	printf("cost per read: %6" PRIu64 " ns (%6" PRIu64 " cycles)\n",
	       cpu_clock_to_wall(best) / iters, (best) / iters);
	return 0;
}

static int sane_performance(struct cpuid_state_t *state)
{
	struct leaf {
		uint32_t eax;
		uint32_t max;
	} leaves[] = {
		{ 0x00000000, 0x1a },
		{ 0x40000000, 0x1a },
		{ 0x80000000, 0x1a }
	};
	size_t leaf_count = sizeof(leaves) / sizeof(leaves[0]);
	size_t i, j;
	for (i = 0; i < leaf_count; i++)
	{
		for (j = 0; j <= leaves[i].max; j++)
		{
			measure_leaf(state, leaves[i].eax + j, 0);
		}
		printf("\n");
	}
	return 0;
}

int sanity_run(struct cpuid_state_t *state)
{
	sanity_handler_t *p = handlers;
	unsigned int ret = 0;
#ifdef TARGET_OS_WINDOWS
	TIMECAPS tc;
	timeGetDevCaps(&tc, sizeof(TIMECAPS));
	timeBeginPeriod(tc.wPeriodMin);
#endif
	while (*p) {
		if ((*p++)(state) != 0)
			ret = p - handlers;
	}
#ifdef TARGET_OS_WINDOWS
	timeEndPeriod(tc.wPeriodMin);
#endif
	return ret;
}

/* vim: set ts=4 sts=4 sw=4 noet: */
